/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <melon/singleton.h>

#include <iostream>
#include <thread>

#include <melon/benchmark.h>
#include <melon/memory.h>
#include <melon/singleton_thread_local.h>
#include <turbo/flags/flag.h>

MELON_GNU_DISABLE_WARNING("-Wdeprecated-declarations")

using namespace melon;

// Benchmarking a normal singleton vs a Meyers singleton vs a Melon
// singleton.  Meyers are insanely fast, but (hopefully) Melon
// singletons are fast "enough."
int* getMeyersSingleton() {
  static auto ret = new int(0);
  return ret;
}

int normal_singleton_value = 0;
int* getNormalSingleton() {
  doNotOptimizeAway(&normal_singleton_value);
  return &normal_singleton_value;
}

struct BenchmarkSingleton {
  int val = 0;
};

void run4Threads(std::function<void()> f) {
  std::vector<std::thread> threads;
  for (size_t i = 0; i < 4; ++i) {
    threads.emplace_back(f);
  }
  for (auto& thread : threads) {
    thread.join();
  }
}

void normalSingleton(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(getNormalSingleton());
  }
}

BENCHMARK(NormalSingleton, n) {
  normalSingleton(n);
}

BENCHMARK(NormalSingleton4Threads, n) {
  run4Threads([=]() { normalSingleton(n); });
}

void meyersSingleton(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(getMeyersSingleton());
  }
}

BENCHMARK(MeyersSingleton, n) {
  meyersSingleton(n);
}

BENCHMARK(MeyersSingleton4Threads, n) {
  run4Threads([=]() { meyersSingleton(n); });
}

struct BenchmarkTag {};
template <typename T, typename Tag = detail::DefaultTag>
using SingletonBenchmark = Singleton<T, Tag, BenchmarkTag>;

struct GetTag {};
struct TryGetTag {};
struct TryGetFastTag {};

SingletonBenchmark<BenchmarkSingleton, GetTag> benchmark_singleton_get;
SingletonBenchmark<BenchmarkSingleton, TryGetTag> benchmark_singleton_try_get;
SingletonBenchmark<BenchmarkSingleton, TryGetFastTag>
    benchmark_singleton_try_get_fast;

void melonSingletonRaw(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    SingletonBenchmark<BenchmarkSingleton, GetTag>::get();
  }
}

BENCHMARK(MelonSingletonRaw, n) {
  melonSingletonRaw(n);
}

BENCHMARK(MelonSingletonRaw4Threads, n) {
  run4Threads([=]() { melonSingletonRaw(n); });
}

void melonSingletonTryGet(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    SingletonBenchmark<BenchmarkSingleton, TryGetTag>::try_get();
  }
}

BENCHMARK(MelonSingletonTryGet, n) {
  melonSingletonTryGet(n);
}

BENCHMARK(MelonSingletonTryGet4Threads, n) {
  run4Threads([=]() { melonSingletonTryGet(n); });
}

void melonSingletonTryGetFast(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    SingletonBenchmark<BenchmarkSingleton, TryGetFastTag>::try_get_fast();
  }
}

BENCHMARK(MelonSingletonTryGetFast, n) {
  melonSingletonTryGetFast(n);
}

BENCHMARK(MelonSingletonTryGetFast4Threads, n) {
  run4Threads([=]() { melonSingletonTryGetFast(n); });
}

void melonSingletonThreadLocal(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    melon::SingletonThreadLocal<BenchmarkSingleton, GetTag>::get().val++;
  }
}

BENCHMARK(MelonSingletonThreadLocal, n) {
  melonSingletonThreadLocal(n);
}

BENCHMARK(MelonSingletonThreadLocal4Threads, n) {
  run4Threads([=]() { melonSingletonThreadLocal(n); });
}

LeakySingleton<BenchmarkSingleton> benchmark_leaky_singleton;

void melonLeakySingleton(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    LeakySingleton<BenchmarkSingleton>::get().val++;
  }
}

BENCHMARK(MelonLeakySingleton, n) {
  melonLeakySingleton(n);
}

BENCHMARK(MelonLeakySingleton4Threads, n) {
  run4Threads([=]() { melonLeakySingleton(n); });
}

int main(int argc, char** argv) {
  melon::parse_args(argc, argv, true);
  gflags::SetCommandLineOptionWithMode(
      "bm_min_usec", "100000", gflags::SET_FLAG_IF_DEFAULT);

  melon::SingletonVault::singleton<BenchmarkTag>()->registrationComplete();
  melon::runBenchmarks();

  return 0;
}
